package com.hvilela;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.imageio.ImageIO;
import javax.swing.ButtonGroup;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.TransferHandler;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import com.hvilela.drawer.Boxes;
import com.hvilela.drawer.Columns;
import com.hvilela.drawer.Lines;
import com.hvilela.filters.GrayscaleFilter;
import com.hvilela.filters.SepiaFilter;

/**
 * @author Henrique de Souza Vilela
 * henrique.vilela@gmail.com
 */
public class Wallpaperer extends JFrame implements ActionListener, ChangeListener, MouseListener, KeyListener {

	protected static final long serialVersionUID = 1L;

	protected List<Entry> entries = new Vector<Entry>();

	protected File directory = new File(".");

	protected Drawer drawer;

	protected Map<JRadioButtonMenuItem, Drawer> drawers = new HashMap<JRadioButtonMenuItem, Drawer>();

	protected ImageFilter filter;

	protected Map<JRadioButtonMenuItem, ImageFilter> filters = new HashMap<JRadioButtonMenuItem, ImageFilter>();

	protected BufferedImage result;

	private JPanel imagePanel;

	private JSpinner widthSpinner;

	private JSpinner heightSpinner;

	private JSpinner borderSpinner;

	private JDialog progress;

	private Entry selected;

	public Wallpaperer() {
		super("Wallpaperer - hvilela.com");

		setLayout(new BorderLayout());

		JMenuBar menuBar = new JMenuBar();

		// File
		JMenu fileMenu = new JMenu("File");

		JMenuItem addImageMenu = new JMenuItem("add image");
		addImageMenu.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				JFileChooser chooser = new JFileChooser(directory);

				if (chooser.showOpenDialog(Wallpaperer.this) == JFileChooser.APPROVE_OPTION) {
					File file = chooser.getSelectedFile();
					try {
						addFile(file);
					} catch (IOException exception) {
						exception.printStackTrace();
					}

					directory = file.getParentFile();

					draw();
				}
			}
		});
		fileMenu.add(addImageMenu);

		JMenuItem addDirectoryMenu = new JMenuItem("add directory");
		addDirectoryMenu.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				JFileChooser chooser = new JFileChooser(directory);
				chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

				if (chooser.showOpenDialog(Wallpaperer.this) == JFileChooser.APPROVE_OPTION) {
					directory = chooser.getSelectedFile();
					addDirectory(directory);

					draw();
				}

				System.out.println("done");
			}
		});
		fileMenu.add(addDirectoryMenu);

		JMenuItem exportMenu = new JMenuItem("export");
		exportMenu.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				JFileChooser chooser = new JFileChooser();
				if (chooser.showSaveDialog(Wallpaperer.this) == JFileChooser.APPROVE_OPTION) {
					try {
						ImageIO.write(result, "png", chooser.getSelectedFile());
					} catch (IOException exception) {
						exception.printStackTrace();
					}
				}
			}
		});
		fileMenu.add(exportMenu);

		JMenuItem clearMenu = new JMenuItem("clear");
		clearMenu.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				entries.clear();

				draw();
			}
		});
		fileMenu.add(clearMenu);

		menuBar.add(fileMenu);

		// Render
		JMenu render = new JMenu("Render");
		ButtonGroup renderGroup = new ButtonGroup();

		JRadioButtonMenuItem boxes = new JRadioButtonMenuItem("boxes");
		boxes.setSelected(true);
		boxes.addActionListener(this);
		render.add(boxes);
		renderGroup.add(boxes);
		drawer = new Boxes();
		drawers.put(boxes, drawer);

		JRadioButtonMenuItem columns = new JRadioButtonMenuItem("columns");
		columns.addActionListener(this);
		render.add(columns);
		renderGroup.add(columns);
		drawers.put(columns, new Columns());

		JRadioButtonMenuItem lines = new JRadioButtonMenuItem("lines");
		lines.addActionListener(this);
		render.add(lines);
		renderGroup.add(lines);
		drawers.put(lines, new Lines());

		menuBar.add(render);

		setJMenuBar(menuBar);

		// Filters
		JMenu filtersMenu = new JMenu("Filters");
		ButtonGroup filtersGroup = new ButtonGroup();

		JRadioButtonMenuItem none = new JRadioButtonMenuItem("None");
		none.addActionListener(this);
		filtersMenu.add(none);
		filtersGroup.add(none);
		filters.put(none, null);

		JRadioButtonMenuItem baw = new JRadioButtonMenuItem("Black and White");
		baw.addActionListener(this);
		filtersMenu.add(baw);
		filtersGroup.add(baw);
		filters.put(baw, new GrayscaleFilter());

		JRadioButtonMenuItem sepia = new JRadioButtonMenuItem("Sepia");
		sepia.addActionListener(this);
		filtersMenu.add(sepia);
		filtersGroup.add(sepia);
		filters.put(sepia, new SepiaFilter());
		menuBar.add(filtersMenu);

		// About
		JMenu help = new JMenu("Help");
		JMenuItem about = new JMenuItem("About");
		about.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				new AboutWindow(Wallpaperer.this).setVisible(true);
			}
		});
		help.add(about);
		menuBar.add(help);

		// Properties
		JPanel gambiarra = new JPanel();
		JPanel properties = new JPanel();
		properties.setPreferredSize(new Dimension(150, 70));
		properties.setLayout(new GridLayout(-1, 2));

		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
		properties.add(new JLabel("width"));
		widthSpinner = new JSpinner(new SpinnerNumberModel(800, 1, Integer.MAX_VALUE, 1));
		widthSpinner.addChangeListener(this);
		properties.add(widthSpinner);

		properties.add(new JLabel("height"));
		heightSpinner = new JSpinner(new SpinnerNumberModel(600, 1, Integer.MAX_VALUE, 1));
		heightSpinner.addChangeListener(this);
		properties.add(heightSpinner);

		properties.add(new JLabel("border"));
		borderSpinner = new JSpinner(new SpinnerNumberModel(2, 0, Integer.MAX_VALUE, 1));
		borderSpinner.addChangeListener(this);
		properties.add(borderSpinner);

		gambiarra.add(properties);
		add(gambiarra, BorderLayout.LINE_START);

		// Image
		imagePanel = new JPanel() {
			@Override
			public void paint(Graphics graphics) {
				if (result != null) {
					graphics.drawImage(result, 0, 0, result.getWidth(), result.getHeight(), null);
					
					if (selected != null) {
						graphics.setColor(new Color(0xFF0000));
						
						for (int i = 0; i < 5; i++) {
							graphics.drawRect(
								(int) selected.getPosition().getX() + i,
								(int) selected.getPosition().getY() + i,
								(int) selected.getArea().getWidth() - i * 2,
								(int) selected.getArea().getHeight() - i * 2
							);
						}
					}
				}
			}
		};
		imagePanel.addMouseListener(this);
		imagePanel.setPreferredSize(new Dimension(800, 600));
		add(new JScrollPane(imagePanel), BorderLayout.CENTER);

		// DnD
		TransferHandler handler = new TransferHandler(null) {
			@Override
			public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
				for (DataFlavor flavor : transferFlavors) {
					if (flavor.isFlavorJavaFileListType()) {
						return true;
					}
				}

				return super.canImport(comp, transferFlavors);
			}

			@Override
			public boolean importData(JComponent component, Transferable transferable) {
				try {
					List<File> files = (List<File>) transferable.getTransferData(transferable.getTransferDataFlavors()[0]);

					for (File file : files) {
						addFile(file);
					}

					draw();
				} catch (Exception exception) {
					exception.printStackTrace();
				}

				return true;
			}
		};
		imagePanel.setTransferHandler(handler);

		// Progress
		progress = new JDialog(this, true);
		progress.setUndecorated(true);
		properties.setBorder(new EmptyBorder(5, 5, 5, 5));
		JProgressBar progressBar = new JProgressBar(JProgressBar.HORIZONTAL);
		progressBar.setIndeterminate(true);
		progress.add(progressBar);
		progress.pack();

		addKeyListener(this);

		setDefaultCloseOperation(EXIT_ON_CLOSE);
		pack();
	}

	protected void addDirectory(File directory) {
		if (directory.isDirectory() && directory.listFiles() != null) {
			for (File file : directory.listFiles()) {
				if (file.isDirectory()) {
					addDirectory(file);
				} else {
					try {
						addFile(file);
					} catch (Exception exception) {
					}
				}
			}
		}
	}

	protected void addFile(File file) throws IOException {
		entries.add(new Entry(file));
	}

	@Override
	public void actionPerformed(ActionEvent event) {
		if (drawers.containsKey(event.getSource())) {
			drawer = drawers.get(event.getSource());
		}

		if (filters.containsKey(event.getSource())) {
			filter = filters.get(event.getSource());
		}

		updateProperties();
	}

	@Override
	public void stateChanged(ChangeEvent event) {
		updateProperties();
	}

	@Override
	public void mouseEntered(MouseEvent event) {}

	@Override
	public void mousePressed(MouseEvent event) {}

	@Override
	public void mouseReleased(MouseEvent event) {
		requestFocus();
		
		for (Entry entry : entries) {
			if (event.getX() >= entry.getPosition().getX()) {
				if (event.getX() <= entry.getPosition().getX() + entry.getArea().getWidth()) {
					if (event.getY() >= entry.getPosition().getY()) {
						if (event.getY() <= entry.getPosition().getY() + entry.getArea().getHeight()) {
							if (selected == entry) {
								selected = null;
							} else {
								selected = entry;
							}

							repaint();
							break;
						}
					}
				}
			}
		}
	}

	@Override
	public void mouseClicked(MouseEvent event) {}
	
	@Override
	public void mouseExited(MouseEvent event) {}

	@Override
	public void keyPressed(KeyEvent event) {
		System.out.println(event.getKeyCode());
		
		if (event.getKeyCode() == KeyEvent.VK_DELETE) {
			if (selected != null) {
				System.out.println("delete");
				
				entries.remove(selected);
				selected = null;
				
				draw();
			}
		} else if (event.getKeyCode() == KeyEvent.VK_Z) {
			if (selected != null) {
				System.out.println("increse");
				
				selected.setWeight(selected.getWeight() + 1);
				
				draw();
			}
		}  else if (event.getKeyCode() == KeyEvent.VK_X) {
			if (selected != null) {
				System.out.println("decrease");
				
				selected.setWeight(Math.max(1, selected.getWeight() - 1));
				
				draw();
			}
		}
	}

	@Override
	public void keyReleased(KeyEvent event) {}

	@Override
	public void keyTyped(KeyEvent event) {}
	
	private void updateProperties() {
		drawer.border = (Integer) borderSpinner.getValue();

		draw();
	}

	protected void draw() {
		new Thread() {
			@Override
			public void run() {
				result = drawer.draw((Integer) widthSpinner.getValue(), (Integer) heightSpinner.getValue(), entries);

				if (filter != null) {
					Image tmp = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(result.getSource(), filter));
					result.getGraphics().drawImage(tmp, 0, 0, null);
				}

				progress.setVisible(false);

				imagePanel.setPreferredSize(new Dimension(result.getWidth(), result.getHeight()));

				repaint();
			}
		}.start();

		progress.setLocationRelativeTo(this);
		progress.setVisible(true);
	}

	public static void main(String[] args) {
		new Wallpaperer().setVisible(true);
	}
}